﻿namespace PageTypeBuilderUI.Plugins.Admin
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Web.UI;
    using System.Web.UI.HtmlControls;
    using System.Web.UI.WebControls;
    using EPiServer;
    using EPiServer.Core;
    using EPiServer.DataAbstraction;
    using EPiServer.PlugIn;
    using EPiServer.Security;
    using EPiServer.UI;
    using log4net;
    using PageTypeBuilder;
    using PageTypeBuilder.Abstractions;
    using PageTypeBuilder.Discovery;
    using PageTypeBuilder.Reflection;
    using PageTypeBuilder.Synchronization.PageDefinitionSynchronization;
    using Reflection;

    [GuiPlugIn(Area = PlugInArea.AdminMenu, DisplayName = "Match properties with PTB",
        RequiredAccess = AccessLevel.Administer, Url = "~/util/PageTypeBuilderUI/PTBPropertyMatch.aspx")]
    public partial class PropertyMatch : SystemPageBase
    {
        
        #region Members

        private static readonly ILog Logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private int _count;
        private List<PageTypeDefinition> _pageTypes;

        #endregion Members

        #region Properties

        /// <summary>
        /// Gets the page types.
        /// </summary>
        /// <value>The page types.</value>
        private IEnumerable<PageTypeDefinition> PageTypes
        {
            get
            {
                return _pageTypes ?? (_pageTypes = (new PageTypeDefinitionLocator(new AppDomainAssemblyLocator())).GetPageTypeDefinitions().ToList());
            }
        }

        #endregion Properties

        #region Event handlers

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Page.PreInit"/> event at the beginning of page initialization.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnPreInit(EventArgs e)
        {
            base.OnPreInit(e);
            //Page.MasterPageFile = ResolveUrlFromUI("MasterPages/EPiServerUI.master");
            Page.MasterPageFile = Path.Combine(UriSupport.InternalUIUrl.Path, "MasterPages/EPiServerUI.Master");
        }

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event to initialize the page.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            RepeaterPageTypes.ItemDataBound += RepeaterPageTypesItemDataBound;
        }

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.Load"/> event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            SystemMessageContainer.Heading = LanguageHelper.Translate("/pageTypeBuilderPropertyMatch/name");
            SystemMessageContainer.Description = LanguageHelper.Translate("/pageTypeBuilderPropertyMatch/description");
            BulkRemovePropertiesButton.Enabled = true;
            BulkUpdatePropertiesButton.Enabled = true;
            ReBind();
        }

        #endregion Event handlers

        #region Helpers

        /// <summary>
        /// Rebinds the page controls.
        /// </summary>
        private void ReBind()
        {
            List<PageType> pageTypes = PageType.List().OrderBy(pageType => pageType.Name).ToList();

            // get all PTB page types and add in if they don't already exist
            foreach (PageTypeDefinition pageType in PageTypes.Where(pageType => !pageTypes.Any(current => string.Equals(current.Name, pageType.GetPageTypeName()))))
            {
                pageTypes.Add(new PageType { ID = -1, Name = pageType.GetPageTypeName() });
            }

            pageTypes = pageTypes.OrderBy(pageType => pageType.Name).ToList();

            RepeaterPageTypes.DataSource = pageTypes;
            RepeaterPageTypes.DataBind();
            DataBind();
        }

        protected void RemoveProperties(object sender, EventArgs e)
        {
            List<int> propertyIds = Request.Form.AllKeys
                .Where(key => key.Contains("RemoveProperty_"))
                .Select(key => int.Parse(key.Substring(key.LastIndexOf("_") + 1))).ToList();

            foreach (PageDefinition pageDefinition in propertyIds.Select(PageDefinition.Load))
                pageDefinition.Delete();

            ReBind();
        }

        protected void UpdateProperties(object sender, EventArgs e)
        {
            List<string> keys = Request.Form.AllKeys
                .Where(key => key.Contains("ChangeProperty_"))
                .Select(key => key.Substring(key.LastIndexOf("_") + 1)).ToList();

            foreach (string key in keys)
            {
                int pageTypeId = int.Parse(key.Substring(0, key.IndexOf(":")));
                int pageDefinitionId = int.Parse(key.Substring(key.IndexOf(":") + 1));

                PageType pageType = PageType.Load(pageTypeId);
                PageDefinition pageDefinition = PageDefinition.Load(pageDefinitionId);

                PageTypePropertyDefinitionLocator locator = new PageTypePropertyDefinitionLocator();
                PageTypeDefinition tempType = this.PageTypes.SingleOrDefault(p => string.Equals(p.GetPageTypeName(), pageType.Name));
                var properties = locator.GetPageTypePropertyDefinitions(new WrappedPageType(pageType), tempType.Type);

                var ptbProperty = properties.SingleOrDefault(p => p.Name == pageDefinition.Name);

                Type propertyType = ptbProperty.PageTypePropertyAttribute.Type ?? (new PageDefinitionTypeMapper(new PageDefinitionTypeRepository(), new NativePageDefinitionsMap())).GetDefaultPropertyType(ptbProperty.PropertyType);

                if (propertyType == null || string.Equals(propertyType.Name, pageDefinition.Type.DefinitionType.Name))
                    continue;

                PageDefinitionType pageDefinitionType = PageDefinitionType.List().Where(current => current.DefinitionType != null && string.Equals(current.DefinitionType.Name, propertyType.Name)).FirstOrDefault();
                pageDefinition.Type = pageDefinitionType;

                if (pageDefinitionType == null) 
                    continue;

                if (pageDefinition.DefaultValueType == DefaultValueType.Value && !IsDefaultValueValid(pageDefinition))
                {
                    InvalidPropertyValueException exception = new InvalidPropertyValueException(pageDefinition.Type.LocalizedName, pageDefinition.DefaultValue);
                    CustomValidator validator = new CustomValidator();
                    validator.ID = Guid.NewGuid().ToString("N");
                    validator.IsValid = false;
                    string formatString = LanguageHelper.Translate("/pageTypeBuilderPropertyMatch/errorMessageFormatString");
                    validator.ErrorMessage = exception.Message + string.Format(formatString, pageType.Name, pageDefinition.Name);
                    Page.Validators.Add(validator);
                }
                else
                    pageDefinition.Save();
            }

            ReBind();
        }

        private bool IsDefaultValueValid(PageDefinition pageDefinition)
        {
            PropertyData propertyData;
            try
            {
                if (string.IsNullOrEmpty(pageDefinition.Type.TypeName))
                    propertyData = PropertyData.CreatePropertyDataObject(pageDefinition.Type.DataType);
                else
                    propertyData = PropertyData.CreatePropertyDataObject(pageDefinition.Type.AssemblyName, pageDefinition.Type.TypeName);

                propertyData.ParseToSelf(pageDefinition.DefaultValue);
            }
            catch
            {
                return false;
            }

            return propertyData.Value != null;
        }

        #endregion

        #region Events

        /// <summary>
        /// Handles the ItemDataBound event of the RepeaterPageTypes control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Web.UI.WebControls.RepeaterItemEventArgs"/> instance containing the event data.</param>
        void RepeaterPageTypesItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            if (e.Item.ItemType != ListItemType.Item && e.Item.ItemType != ListItemType.AlternatingItem)
                return;

            _count = 0;
            Repeater repeater = e.Item.FindControl("RepeaterProperties") as Repeater;
            PageType pageType = e.Item.DataItem as PageType;

            if (repeater == null)
                return;

            repeater.ItemDataBound += RepItemDataBound;

            PageDefinitionCollection definitions = new PageDefinitionCollection();

            if (pageType != null)
            {
                if (pageType.ID == -1)
                {
                    _count = 1;
                    PlaceHolder placeHolder = e.Item.FindControl("PropertiesPlaceHolder") as PlaceHolder;
                    placeHolder.Visible = false;
                    placeHolder = e.Item.FindControl("PageTypeNotFoundPlaceHolder") as PlaceHolder;
                    placeHolder.Visible = true;
                }
                else
                {
                    definitions.AddRange(pageType.Definitions);

                    IPageType wrappedPageType = new WrappedPageType(pageType);
                    Type pageTypeType = PageTypeResolver.Instance.GetPageTypeType(pageType.ID);

                    if (pageTypeType != null)
                    {
                        PageTypePropertyDefinitionLocator locator = new PageTypePropertyDefinitionLocator();
                        PageTypeDefinition tempType = PageTypes.SingleOrDefault(p => p.GetPageTypeName() == pageType.Name);
                        List<PageTypePropertyDefinition> properties = locator.GetPageTypePropertyDefinitions(new WrappedPageType(pageType), tempType.Type).ToList();

                        foreach (PageTypePropertyDefinition property in properties.Where(property => !definitions.Any(current => string.Equals(current.Name, property.Name, StringComparison.OrdinalIgnoreCase))))
                        {
                            definitions.Add(new PageDefinition { Name = property.Name, PageTypeID = pageType.ID });
                        }

                        // need to add property group properties too if they don't exist!!!!!
                        List<PropertyInfo> propertyGroupProperties = pageTypeType.GetPageTypePropertyGroupProperties()
                            .ToList();

                        foreach (PropertyInfo property in propertyGroupProperties.Where(property => property.PropertyType.BaseType == typeof(PageTypePropertyGroup)))
                        {
                            PageTypePropertyGroupAttribute pageTypePropertyGroupAttribute = GetPropertyAttribute<PageTypePropertyGroupAttribute>(property);
                            List<PageTypePropertyDefinition> propertyDefinitions = GetPropertyGroupPropertyDefinitions(wrappedPageType, property).ToList();

                            foreach (PageTypePropertyDefinition propertyDefinition in propertyDefinitions)
                            {
                                if (!definitions.Any(current => string.Equals(current.Name, propertyDefinition.Name, StringComparison.OrdinalIgnoreCase)))
                                {
                                    definitions.Add(new PageDefinition { Name = propertyDefinition.Name, PageTypeID = pageType.ID });
                                }
                            }
                        }
                    }    
                }
            }
                
            if (pageType != null)
                repeater.DataSource = definitions;

            repeater.DataBind();
        }

        internal T GetPropertyAttribute<T>(PropertyInfo propertyInfo)
        {
            return propertyInfo.GetCustomAttributes<T>().FirstOrDefault();
        }

        private IEnumerable<PageTypePropertyDefinition> GetPropertyGroupPropertyDefinitions(IPageType pageType, PropertyInfo propertyGroupProperty)
        {
            PageTypePropertyGroupAttribute groupAttribute = GetPropertyAttribute<PageTypePropertyGroupAttribute>(propertyGroupProperty);
            PropertyInfo[] propertyGroupProperties = propertyGroupProperty.PropertyType.GetPublicOrPrivateProperties();

            foreach (PropertyInfo property in propertyGroupProperties)
            {
                PageTypePropertyAttribute attribute = GetPropertyAttribute<PageTypePropertyAttribute>(property);

                if (attribute == null)
                    continue;

                string resolvedPropertyName = ResolvePropertyName(propertyGroupProperty.Name, property.Name);
                attribute = AdjustPropertyGroupAttributeProperties(attribute, groupAttribute);

                yield return new PageTypePropertyDefinition(resolvedPropertyName, property.PropertyType, pageType, attribute);
            }
        }

        public static string ResolvePropertyName(string currentHierarchy, string propertyName)
        {
            return !string.IsNullOrEmpty(currentHierarchy)
                    ? string.Concat(currentHierarchy, "-", propertyName)
                    : propertyName;
        }

        private PageTypePropertyAttribute AdjustPropertyGroupAttributeProperties(PageTypePropertyAttribute attribute, PageTypePropertyGroupAttribute groupAttribute)
        {
            if (groupAttribute != null)
            {
                if (!string.IsNullOrEmpty(groupAttribute.EditCaptionPrefix))
                    attribute.EditCaption = groupAttribute.EditCaptionPrefix + attribute.EditCaption;

                if (groupAttribute.StartSortOrderFrom > 0)
                    attribute.SortOrder = groupAttribute.StartSortOrderFrom + attribute.SortOrder;
            }
            return attribute;
        }

        /// <summary>
        /// Reps the item data bound.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Web.UI.WebControls.RepeaterItemEventArgs"/> instance containing the event data.</param>
        void RepItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            if (e.Item.ItemType == ListItemType.Footer)
            {
                if (_count == 0 && CheckBoxOnlyMissmatch.Checked)
                    ((Control)sender).Parent.Parent.Visible = false;

                return;
            }

            if (e.Item.ItemType != ListItemType.Item && e.Item.ItemType != ListItemType.AlternatingItem)
                return;

            bool missmatch = false;
            string highlightClass = string.Empty;
            PageDefinition property = e.Item.DataItem as PageDefinition;

            if (property != null)
            {
                PageType pageType = PageType.Load(property.PageTypeID);
                PageTypePropertyDefinitionLocator locator = new PageTypePropertyDefinitionLocator();

                PageTypeDefinition tempType = PageTypes.SingleOrDefault(p => p.GetPageTypeName() == pageType.Name);

                if (tempType == null)
                {
                    Literal ltl = e.Item.FindControl("LiteralMissmatch") as Literal;

                    if (ltl != null)
                        ltl.Text = LanguageHelper.Translate("/pageTypeBuilderPropertyMatch/couldNotFindPropertyType");

                    return;
                }

                List<PageTypePropertyDefinition> props = locator.GetPageTypePropertyDefinitions(new WrappedPageType(pageType), tempType.Type).ToList();
                var ptbproperty = props.SingleOrDefault(p => p.Name == property.Name);

                if (ptbproperty == null)
                {
                    bool validProperty = false;

                    if (Configuration.PageTypeBuilderUIConfiguration.GetConfiguration().EnableCustomPropertyGroupRendering)
                    {
                        string containerPropertyName = property.Name.Replace("_Container", string.Empty);

                        IPageType wrappedPageType = new WrappedPageType(pageType);
                        Type pageTypeType = PageTypeResolver.Instance.GetPageTypeType(pageType.ID);

                        List<PropertyInfo> propertyGroupProperties = pageTypeType.GetPageTypePropertyGroupProperties()
                            .ToList();

                        if (propertyGroupProperties.Count > 0)
                            validProperty = propertyGroupProperties.Any(current => string.Equals(current.Name, containerPropertyName));
                    }

                    if (!validProperty)
                    {
                        LinkButton remove = e.Item.FindControl("LinkButtonRemove") as LinkButton;

                        if (remove != null)
                        {
                            remove.Text = LanguageHelper.Translate("/pageTypeBuilderPropertyMatch/removeProperty"); ;
                            remove.CommandArgument = property.ID.ToString();
                            remove.Click += RemoveClick;
                        }

                        missmatch = true;
                        _count++;

                        Literal ltl = e.Item.FindControl("LiteralMissmatch") as Literal;

                        if (ltl != null)
                            ltl.Text = LanguageHelper.Translate("/pageTypeBuilderPropertyMatch/couldNotFindPageTypeBuilderProperty");

                        // add check box for bulk removal
                        CheckBox checkBox = e.Item.FindControl("RemovePropertyCheckBox") as CheckBox;
                        checkBox.Visible = true;
                        checkBox.ID = string.Format("RemoveProperty_{0}", property.ID);
                        checkBox.Attributes.Add("onclick", string.Format("javascript:UpdateSelectAllCheckBox('RemovePropertySelectAll', '{0}', 'RemoveProperty_');",
                            BulkRemovePropertiesButton.ClientID));
                        checkBox.Checked = Request.Form.AllKeys.Any(current => current.Contains(checkBox.ID));

                        if (checkBox.Checked)
                        {
                            highlightClass = "ptbDeleteRow";
                            //BulkRemovePropertiesButton.Enabled = true;
                        }
                    }
                }
                else
                {
                    Literal l = e.Item.FindControl("LiteralType") as Literal;

                    if (!pageType.Definitions.Any(current => string.Equals(current.Name, ptbproperty.Name, StringComparison.OrdinalIgnoreCase)))
                    {
                        Literal ltl = e.Item.FindControl("LiteralMissmatch") as Literal;

                        if (ltl != null)
                            ltl.Text = LanguageHelper.Translate("/pageTypeBuilderPropertyMatch/propertyDoesNotExistInPageDefinition");

                        try
                        {
                            Type t = ptbproperty.PageTypePropertyAttribute.Type ?? (new PageDefinitionTypeMapper(new PageDefinitionTypeRepository(), new NativePageDefinitionsMap())).GetDefaultPropertyType(ptbproperty.PropertyType);
                            string typename = (t != null ? t.Name : LanguageHelper.Translate("/pageTypeBuilderPropertyMatch/couldNotGetType"));

                            if (l != null)
                                l.Text = typename;
                        }
                        catch (Exception exception)
                        {
                            Logger.Error(exception);
                        }

                        missmatch = true;
                        _count++;
                    }
                    else
                    {
                        try
                        {
                            Type t = ptbproperty.PageTypePropertyAttribute.Type ?? (new PageDefinitionTypeMapper(new PageDefinitionTypeRepository(), new NativePageDefinitionsMap())).GetDefaultPropertyType(ptbproperty.PropertyType);
                            string typename = (t != null ? t.Name : LanguageHelper.Translate("/pageTypeBuilderPropertyMatch/couldNotGetType"));

                            if (l != null)
                                l.Text = typename;

                            if (typename != property.Type.DefinitionType.Name)
                            {
                                Literal ltl = e.Item.FindControl("LiteralMissmatch") as Literal;

                                if (ltl != null)
                                    ltl.Text = LanguageHelper.Translate("/pageTypeBuilderPropertyMatch/typeMismatch");

                                missmatch = true;
                                _count++;

                                // http://local.sn.se/EPiUi/CMS/Admin/EditPageTypeField.aspx?typeId=116
                                HyperLink link = e.Item.FindControl("HyperLinkAction") as HyperLink;

                                if (link != null)
                                {
                                    link.Target = "_blank";
                                    link.Text = LanguageHelper.Translate("/pageTypeBuilderPropertyMatch/editProperty");
                                    link.NavigateUrl = UriSupport.ResolveUrlFromUIBySettings("Admin/") + "EditPageTypeField.aspx?typeId=" + property.ID;
                                }

                                // add check box for bulk type update
                                CheckBox checkBox = e.Item.FindControl("ChangePropertyCheckBox") as CheckBox;
                                checkBox.Visible = true;
                                checkBox.ID = string.Format("ChangeProperty_{0}:{1}", pageType.ID, property.ID);
                                checkBox.Attributes.Add("onclick", string.Format("javascript:UpdateSelectAllCheckBox('ChangePropertySelectAll', '{0}', 'ChangeProperty_');",
                                    BulkUpdatePropertiesButton.ClientID));
                                checkBox.Checked = Request.Form.AllKeys.Any(current => current.Contains(checkBox.ID));

                                if (checkBox.Checked)
                                {
                                    highlightClass = "ptbUpdateRow";
                                    //BulkUpdatePropertiesButton.Enabled = true;
                                }
                            }
                        }
                        catch (Exception exception)
                        {
                            Logger.Error(exception);
                        }
                    }
                }
            }


            if (!string.IsNullOrEmpty(highlightClass))
            {
                HtmlTableRow tableRow = e.Item.FindControl("TableRow") as HtmlTableRow;
                tableRow.Attributes.Add("class", highlightClass);
            }

            if (CheckBoxOnlyMissmatch.Checked && !missmatch)
            {
                e.Item.Visible = false;
            }
        }

        /// <summary>
        /// Handles the Click event of the remove control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void RemoveClick(object sender, EventArgs e)
        {
            LinkButton btn = sender as LinkButton;

            if (btn == null)
                return;

            int id = int.Parse(btn.CommandArgument);
            PageDefinition pageDefinition = PageDefinition.Load(id);
            pageDefinition.Delete();
            ReBind();
        }

        #endregion

    }
}
